//
// Created by tang on 2021/11/27.
//

#include <cstdio>
#include <cstdlib>
#include <string>
#include <cassert>
#include "gl_tools.h"

static GLuint CreateShader(const char *src, GLenum type) {
    GLuint shader;
    GL_CHECK(shader = glCreateShader(type));
    GL_CHECK(glShaderSource(shader, 1, &src, nullptr));
    GL_CHECK(glCompileShader(shader));
    GLint status;
    GL_CHECK(glGetShaderiv(shader, GL_COMPILE_STATUS, &status));
    if (GL_TRUE != status) {
        GLint log_len;
        GL_CHECK(glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len));
        if (log_len > 0) {
            char *buff = new char [log_len];
            GL_CHECK(glGetShaderInfoLog(shader, log_len, &log_len, buff));
            fprintf(stderr, "%s Shader Compile Error: %s\n", type == GL_VERTEX_SHADER ? "Vertex" : "Fragment", buff);
            delete[] buff;
            abort();
        }
    }
    return shader;
}

static std::string ReadAll(const char *path) {
    FILE *file = fopen(path, "rb");
    assert(file != nullptr);
    fseek(file, 0, SEEK_END);
    size_t len = ftell(file);
    assert(len > 0);
    std::string ret(len, ' ');
    fseek(file, 0, SEEK_SET);
    fread(ret.data(), len, 1, file);
    fclose(file);

    return ret;
}

std::pair<std::string,std::string> AnalysisSource(const std::string &sources) {
    std::pair<std::string, std::string> ret; // vertex shader and fragment shader

    // headers { @vertex, @fragment } size { 7, 9 }
    size_t p1, p2;
    p1 = sources.find("@vertex");
    p2 = sources.find("@fragment");
    assert(p1 < sources.length() && p2 < sources.length());
    if (p1 < p2) {
        // vertex first
        // find first '\n'
        size_t pos = sources.find('\n', p1);
        assert(pos < p2);
        ret.first = sources.substr(pos + 1, p2 - pos - 1);
        pos = sources.find('\n', p2);
        ret.second = sources.substr(pos + 1);
    } else {
        // fragment first
        // find first '\n'
        size_t pos = sources.find('\n', p2);
        assert(pos < p1);
        ret.first = sources.substr(pos + 1, p1 - pos - 1);
        pos = sources.find('\n', p1);
        ret.second = sources.substr(pos + 1);
    }
    return ret;
}

Program::Program(const char *prog_file) {
    const std::string sources = ReadAll(prog_file);
    auto vert_frag = AnalysisSource(sources);
    GL_CHECK(m_prog_ = glCreateProgram());
    GLuint vert, frag;
    vert = CreateShader(vert_frag.first.c_str(), GL_VERTEX_SHADER);
    GL_CHECK(glAttachShader(m_prog_, vert));
    frag = CreateShader(vert_frag.second.c_str(), GL_FRAGMENT_SHADER);
    GL_CHECK(glAttachShader(m_prog_, frag));
    GL_CHECK(glLinkProgram(m_prog_));
    GL_CHECK(glDeleteShader(vert));
    GL_CHECK(glDeleteShader(frag));

    GLint linked;
    GL_CHECK(glGetProgramiv(m_prog_, GL_LINK_STATUS, &linked));
    if (GL_TRUE != linked) {
        GL_CHECK(glGetProgramiv(m_prog_, GL_INFO_LOG_LENGTH, &linked));
        if (linked) {
            char *buff = new char[linked];
            GL_CHECK(glGetProgramInfoLog(m_prog_, linked, &linked, buff));
            fprintf(stderr, "Program link error: %s\n", buff);
            delete []buff;
            abort();
        }
    }
}

Program::~Program() {
    GL_CHECK(glUseProgram(0));
    GL_CHECK(glDeleteProgram(m_prog_));
}

void Program::Use() const {
    GL_CHECK(glUseProgram(m_prog_));
}

GLint Program::AttribLocation(const char *name) const {
    GLint ret;
    GL_CHECK(ret = glGetAttribLocation(m_prog_, name));
    return ret;
}

GLint Program::UniformLocation(const char *name) const {
    GLint ret;
    GL_CHECK(ret = glGetUniformLocation(m_prog_, name));
    return ret;
}

VBO::VBO(GLenum type, GLenum usage): m_type_(type), m_usage_(usage) {
    GL_CHECK(glGenBuffers(1, &m_handle_));
}

VBO::~VBO() {
    GL_CHECK(glDeleteBuffers(1, &m_handle_));
}

void VBO::Update(GLintptr offset, const void *data, GLsizeiptr size) const {
    GL_CHECK(glBufferSubData(m_type_, offset, size, data));
}

void VBO::Bind() const {
    GL_CHECK(glBindBuffer(m_type_, m_handle_));
}

void VBO::UnBind() const {
    GL_CHECK(glBindBuffer(m_type_, 0));
}

void VBO::Allocate(GLsizeiptr new_size, const void *init_data) const {
    GL_CHECK(glBufferData(m_type_, new_size, init_data, m_usage_));
}



